import {supportFunction} from "../utils/supportFunction";
import {stringUtils} from "../utils/stringUtils";
import {mergeToMidware, MidwareConfig, CustomPlayerConfig} from "./midware";
import jQuery from "jquery";

let componentContext = {}, moduleInstance = {}, injectModels = {};
let namespaceQueue = [], deferreds = {}, waitting = {};
let namespaceVersion = {};
/*组件配置*/
const componentConfig = function (settings) {
    //在线场景才需要,离线场景目录是固定的
    if(MidwareConfig.Online) {
        if(settings && typeof settings === 'object') {
            //可传入配置
            jQuery.extend(true, namespaceVersion, settings);
        } else {
            //从config中获取
            for(const key in CustomPlayerConfig.Presenters) {
                const temp = CustomPlayerConfig.Presenters[key];
                if(temp.code.indexOf('component-') === 0) {
                    let ns = temp.code.substring(10);
                    if (!supportFunction.hasProp(namespaceVersion, ns)) {
                        namespaceVersion[ns] = temp.version;
                    }
                }
            }
        }
    }
    return jQuery.Deferred().resolve();
};
/*组件定义*/
const componentDefine = function (key, deps, main) {
    let keyInfo = normalizeComponentName(key);
    let dependKeyInfo = [];
    if (typeof deps === 'function' && main === undefined) {
        main = deps;
    }
    let options = {
        depends: [],
        visibility: "public"
    };
    if (typeof deps === 'object' && typeof main === 'function') {
        if (Array.prototype.isArray(deps)) {
            //如果是数组
            options.depends = deps;
        } else {
            //如果是对象
            options.visibility = supportFunction.hasProp(deps, 'visibility') ?
                supportFunction.getOwn(deps, 'visibility') : 'public';
            options.depends = supportFunction.hasProp(deps, 'depends') ?
                supportFunction.getOwn(deps, 'depends') : [];
        }
    }
    //如果当前ns没有被加载过
    if (!supportFunction.hasProp(componentContext, keyInfo.ns)) {
        namespaceQueue.push({ns: keyInfo.ns, loaded: false});
        //实例化ns对应的上下文对象
        componentContext[keyInfo.ns] = new ComponentContext(keyInfo);
    }
    options.depends.forEach((item) => {
        let temp = normalizeComponentName(item);
        //如果依赖的ns没有被加载过
        if (namespaceQueue.findIndex((item) => {
                return item.ns === temp.ns
            }) === -1) {
            namespaceQueue.push({ns: temp.ns, loaded: false});
        }
        if (!supportFunction.hasProp(componentContext, temp.ns)) {
            componentContext[temp.ns] = new ComponentContext(temp);
        }
        dependKeyInfo.push(temp);
    });
    componentContext[keyInfo.ns].push(keyInfo.key, dependKeyInfo, main, options);
};
/*组件请求*/
const componentRequire = function (key) {
    let loadKeys = [], requireType = 'component';
    if (typeof key === 'string') {
        //请求单个组件
        let keyInfo = normalizeComponentName(key);
        loadKeys.push(keyInfo);
    } else if (typeof key === 'object' && Array.prototype.isArray(key)) {
        requireType = 'load';
        //请求多个复数组件
        key.forEach((item) => {
            let keyInfo = normalizeComponentName(item);
            if (loadKeys.findIndex((item) => {
                    return item.ns === keyInfo.ns && item.key === keyInfo.key
                }) === -1) {
                //避免组件被重复请求
                loadKeys.push(keyInfo);
            }
        });
    }

    let promises = [];
    loadKeys.forEach((ki) => {
        if (!supportFunction.hasProp(componentContext, ki.ns) && !supportFunction.hasProp(waitting, ki.ns)) {
            namespaceQueue.push({ns: ki.ns, loaded: false});
            //实例化ns对应的上下文对象, 并开始加载
            componentContext[ki.ns] = new ComponentContext(ki);
            componentContext[ki.ns].config();
        }
        //将当前命名空间的延迟对象加入队列
        promises.push(getDefer(ki.ns).promise);
    });
    if (requireType === 'component') {
        //当个异步组件请求
        return () => {
            return Promise.all(promises).then(() => {
                delete waitting[loadKeys[0].ns];
                //所有的延迟对象都resolve
                return componentContext[loadKeys[0].ns].load(loadKeys[0].key);
            });
        }
    } else {
        //多个异步组件请求
        return Promise.all(promises).then(() => {
            let components = {};
            loadKeys.forEach((loadKey) => {
                delete waitting[loadKey.ns];
                components[(loadKey.ns + ':' + loadKey.key)] = componentContext[loadKey.ns].load(loadKey.key);
            });
            return components;
        })
    }
};

class ComponentContext {
    constructor(keyInfo) {
        this._components = {};
        this._classes = {};
        this._as = keyInfo.as;
        this._ns = keyInfo.ns;
        this._hasBeenLoaded = false;

        if(this._as) {
            const temp = this._ns.split('.');
            this._nsPath = temp[0];
        }
    }

    get PackageLoaded() {
        return this._hasBeenLoaded;
    }

    set PackageLoaded(value) {
        this._hasBeenLoaded = value;
    }

    push(key, depends, main, options) {
        if (!supportFunction.hasProp(this._components, key)) {
            this._components[key] = {depends: depends, main: main, visibility: options.visibility};
        }
    }

    config() {
        if (this._hasBeenLoaded) {
            console.warn("this component namespace has been loaded!");
            return;
        }
        let fileName = (this._as || this._ns).toLowerCase() + '.dist.js';
        //如果是开发的环境, 加载组件库时需要添加dist路径
        let version = supportFunction.getOwn(namespaceVersion, (this._nsPath || this._ns)) || (MidwareConfig.RunEnvironment === 'development' ? "dist" : "");
        let nsPath;
        if (MidwareConfig.BasePath === '') {
            nsPath = stringUtils.urlJoin(MidwareConfig.ComponentBasePath,
                'component-' + (this._nsPath || this._ns), version, fileName);
        } else {
            nsPath = stringUtils.urlJoin(MidwareConfig.BasePath, MidwareConfig.ComponentBasePath,
                'component-' + (this._nsPath || this._ns), version, fileName);
        }
        let defer = getDefer(this._ns);
        this._loadScript({id: this._ns, url: nsPath});
        return defer.promise;
    }

    /**
     * 加载当前命名空间下, 指定键值的组件
     * @param key 组件的键值
     */
    load(key) {
        if (!supportFunction.hasProp(this._components, key)) {
            throw new Error('the namespace ' + this._ns + ' not contain component names ' + key);
        }
        let config = this._components[key], refs = [];
        //如果请求的组件是私有组件
        if (config.visibility === 'private') {
            throw Error('The component ' + key + ' is not available for private components');
        }
        if (config.depends && Array.prototype.isArray(config.depends)) {
            //如果存在依赖组件
            config.depends.forEach((item) => {
                //依赖当前命名空间
                if (item.ns === this._ns) {
                    this.get(item.key, refs);
                } else {
                    getComponent(item.ns, item.key, refs);
                }
            });
        }
        return config.main.apply(window, refs);
    }

    get(key, refs) {
        if (supportFunction.hasProp(this._classes, key)) {
            refs.push(this._classes[key]);
        } else {
            let config = this._components[key];
            let tmpRefs = [];
            if (config.depends && config.depends.length > 0) {
                config.depends.forEach((item) => {
                    if (item.ns === this._ns) {
                        this.get(item.key, tmpRefs);
                    } else {
                        getComponent(item.ns, item.key, tmpRefs);
                    }
                });
            }
            let clazz = config.main.apply(window, tmpRefs);
            this._classes[key] = clazz;
            refs.push(clazz);
        }
    }

    /**
     * 获取指定组件的配置
     * @param key 组件名称
     */
    getConfig(key) {
        return supportFunction.getOwn(this._components, key);
    }

    _loadScript(map) {
        if (waitting[map.id]) {
            return;
        }
        waitting[map.id] = true;
        let script = document.createElement('script');
        script.setAttribute('data-extend-module', map.id);
        script.type = 'text/javascript';
        script.charset = 'utf-8';
        script.async = true;
        script.src = map.url;
        script.addEventListener('load', this._onScriptLoaded.bind(this), false);
        script.addEventListener('error', this._onScriptError.bind(this), false);
        document.head.appendChild(script);
    }

    //当前组件包完成加载
    _onScriptLoaded() {
        let temp = namespaceQueue.find((item) => {
            return item.ns === this._ns
        });
        temp.loaded = true;

        let next = namespaceQueue.find((item) => {
            return item.loaded === false
        });
        if (next) {
            //如果还存在需要加载的依赖组件包
            waitForDepend(next.ns, getDefer(this._ns));
        } else {
            //以没有需要在加载的对象了
            getDefer(this._ns).resolve();
        }
    }

    _onScriptError() {
        getDefer(this._ns).reject();
    }
}

function normalizeComponentName(name) {
    if (typeof name !== "string" || name.indexOf(':') < 0) {
        throw new Error("invalid component name");
    }
    let temp = name.split(':');
    if (temp.length !== 2) {
        throw new Error('the component name only contain namespace and names');
    }
    //如果NS中包含'.'
    if(temp[0].indexOf('.') > 0) {
        let temp2 = temp[0].split('.');
        if(temp2.length !== 2) {
            throw new Error('the component namespace only contain one dot');
        }
        //返回内容包含命名空间, 控件集, 控件名称
        return {ns: temp[0], as: temp2[1], key: temp[1]};
    }
    //返回内容包含命名空间, 控件名称
    return {ns: temp[0], key: temp[1]};
}

function callDepend(ns, defer) {
    if (!supportFunction.hasProp(componentContext, ns)) {
        throw Error('component context not exists!');
    }
    if (!componentContext[ns].PackageLoaded) {
        //如果还没有加载过
        return componentContext[ns].config();
    } else {
        //如果已经加载过
        let nextNs = namespaceQueue.shift();
        if (nextNs) {
            callDepend(nextNs);
        } else {
            componentContext[ns].load();
        }
    }
}

function waitForDepend(ns, defer) {
    if (waitting[ns]) {
        let def = getDefer(ns), finishPromise = def.finishPromise;
        def.finishPromise = function () {
            finishPromise();
            defer.resolve();
        };
    }
    defer.dependCount += 1;
    callDepend(ns).then(function () {
        defer.dependCount -= 1;
        defer.finishPromise();
        defer.resolve();
    });
}

function makeDefer(ns) {
    if (supportFunction.hasProp(deferreds, ns)) {
        throw new Error('def has exists')
    }

    let defer = {};
    defer.promise = new Promise(function (resolve, reject) {
        defer.resolve = resolve;
        defer.reject = reject;
        defer.finishPromise = function () {
            defer.dependCount -= 1;
            componentContext[ns].PackageLoaded = true;
        };
    });
    defer.namespace = ns;
    defer.dependCount = 0;
    return defer;
}

function getDefer(ns) {
    let defer;
    if (ns) {
        defer = supportFunction.hasProp(deferreds, ns) && deferreds[ns];
        if (!defer) {
            defer = deferreds[ns] = makeDefer(ns);
        }
    }
    return defer;
}

function getComponent(ns, key, refs) {
    if (!supportFunction.hasProp(componentContext, ns)) {
        throw new Error('the namespace ' + ns + ' not loaded!');
    }
    let config = componentContext[ns].getConfig(key);
    if (!config) {
        throw Error('The component does not exist in the specified namespace');
    }
    if (config.visibility === 'private') {
        throw Error('The component ' + key + ' is not available for private components');
    }
    return componentContext[ns].get(key, refs);
}

/**
 * 定义被管理的组件的方法
 */
function ManagedComponent(options) {
    let doBeforeRun = undefined, doBeforeDestroy = undefined, injectModel = undefined;
    if (options && typeof options.beforeRun === 'function') {
        doBeforeRun = options.beforeRun;
    }
    if (options && typeof options.beforeDestroy === 'function') {
        doBeforeDestroy = options.beforeDestroy;
    }
    if (options && options.injectModel) {
        injectModel = options.injectModel;
    }
    return function (target) {
        Object.defineProperty(target.prototype, 'beforeDestroy', {
            configurable: false,
            enumerable: true,
            writable: false,
            value: function (...args) {
                removeModuleInstance(this.metaInfo.name, this._CLASS_ID_);
                if (doBeforeDestroy) {
                    doBeforeDestroy.apply(this, args);
                }
            }
        });
        Object.defineProperty(target.prototype, "beforeRun", {
            configurable: false,
            enumerable: true,
            writable: false,
            value: function (...args) {
                pushModuleInstance(this.metaInfo.name, this._CLASS_ID_, this);
                if (doBeforeRun) {
                    doBeforeRun.apply(this, args);
                }
            }
        });
        //执行beforeRun方法
        if (typeof target.prototype.run === 'function') {
            let doRun = target.prototype.run;
            target.prototype.run = function (...args) {
                //处理外部注入的模型
                if (this.QueryString && this.QueryString['app']) {
                    let app = this.QueryString['app'];
                    if (injectModels[app] && injectModels[app][this.metaInfo.name]) {
                        injectModel = !injectModel ?
                            injectModels[app][this.metaInfo.name] :
                            $.extend(true, injectModel, injectModels[app][this.metaInfo.name]);
                    }
                }
                //在执行基类Run方法执行时注入额外的对象
                args.push(injectModel);
                //执行运行前的方法
                target.prototype.beforeRun.apply(this, args);
                //执行Run方法
                return doRun.apply(this, args);
            };
        }
        //执行beforeDestroy方法
        if (typeof target.prototype.destroy === 'function') {
            let doDestroy = target.prototype.destroy;
            target.prototype.destroy = function (...args) {
                target.prototype.beforeDestroy.apply(this, args);
                return doDestroy.apply(this.args);
            }
        }
    };
}

function pushModuleInstance(key, id, instance) {
    if (!supportFunction.hasProp(moduleInstance, key)) {
        moduleInstance[key] = {};
    }
    if (!supportFunction.hasProp(moduleInstance[key], id)) {
        moduleInstance[key][id] = instance;
    }
}

function removeModuleInstance(key, id) {
    if (supportFunction.hasProp(moduleInstance, key) && supportFunction.hasProp(moduleInstance[key], id)) {
        delete moduleInstance[key][id];
    }
    if (supportFunction.isEmptyObject(moduleInstance[key])) {
        delete moduleInstance[key];
    }
}

/**定义对外暴露的全局变量**/
export function initSwarm() {
    mergeToMidware('injectModel', function (app, key, model) {
        if (!supportFunction.hasProp(injectModels, app)) {
            injectModels[app] = {};
        }
        if (model) {
            injectModels[app][key] = model;
        }
    });
    mergeToMidware('componentDefine', componentDefine);
    mergeToMidware('componentRequire', componentRequire);
    mergeToMidware('componentConfig', componentConfig);
    mergeToMidware('ManagedComponent', ManagedComponent);
}
